/*
 * This file is part of HE_Mesh, a library for creating and manipulating meshes.
 * It is dedicated to the public domain. To the extent possible under law,
 * I , Frederik Vanhoutte, have waived all copyright and related or neighboring
 * rights.
 *
 * This work is published from Belgium. (http://creativecommons.org/publicdomain/zero/1.0/)
 *
 */
package wblut.hemesh;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.zip.GZIPOutputStream;

import gnu.trove.map.TLongIntMap;
import gnu.trove.map.hash.TLongIntHashMap;
import wblut.geom.WB_Coord;

class HET_WriterOBJ {
	/**
	 *
	 */
	protected static OutputStream objStream;
	/**
	 *
	 */
	protected static PrintWriter objWriter;
	/**
	 *
	 */
	protected static OutputStream mtlStream;
	/**
	 *
	 */
	protected static PrintWriter mtlWriter;
	/**
	 *
	 */
	protected static int numVerticesWritten = 0;
	/**
	 *
	 */
	protected static int numNormalsWritten = 0;

	/**
	 * Begin save.
	 *
	 * @param fn
	 *            the fn
	 * @param name
	 */
	static void beginSave(final String fn, final String name) {
		try {
			objStream = createOutputStream(new File(fn, name + ".obj"));
			mtlStream = createOutputStream(new File(fn, name + ".mtl"));
			startWriting();
		} catch (final Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 *
	 *
	 * @param file
	 * @return
	 * @throws IOException
	 */
	static OutputStream createOutputStream(final File file) throws IOException {
		if (file == null) {
			throw new IllegalArgumentException("file can't be null");
		}
		createDirectories(file);
		OutputStream stream = new FileOutputStream(file);
		if (file.getName().toLowerCase().endsWith(".gz")) {
			stream = new GZIPOutputStream(stream);
		}
		return stream;
	}

	/**
	 *
	 *
	 * @param file
	 */
	static void createDirectories(final File file) {
		try {
			final String parentName = file.getParent();
			if (parentName != null) {
				final File parent = new File(parentName);
				if (!parent.exists()) {
					parent.mkdirs();
				}
			}
		} catch (final SecurityException se) {
			System.err.println("No permissions to create " + file.getAbsolutePath());
		}
	}

	/**
	 * End save.
	 */
	static void endSave() {
		try {
			objWriter.flush();
			objWriter.close();
			objStream.flush();
			objStream.close();
			mtlWriter.flush();
			mtlWriter.close();
			mtlStream.flush();
			mtlStream.close();
		} catch (final IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Face.
	 *
	 * @param indices
	 *
	 */
	static void writeFace(final int... indices) {
		objWriter.print("f ");
		for (int i = 0; i < indices.length - 1; i++) {
			objWriter.print(indices[i] + " ");
		}
		objWriter.println(indices[indices.length - 1]);
	}

	/**
	 *
	 *
	 * @param i
	 * @param c
	 */
	static void writeFaceColor(final int i, final int c) {
		mtlWriter.println("newmtl f" + i);
		mtlWriter.println("Kd " + red(c) + " " + green(c) + " " + blue(c));
		mtlWriter.println("illum 0");
	}

	/**
	 *
	 *
	 * @param what
	 * @return
	 */
	static final float red(final int what) {
		return (what >> 16 & 0xff) / 255.0f;
	}

	/**
	 *
	 *
	 * @param what
	 * @return
	 */
	static final float green(final int what) {
		return (what >> 8 & 0xff) / 255.0f;
	}

	/**
	 *
	 *
	 * @param what
	 * @return
	 */
	static final float blue(final int what) {
		return (what & 0xff) / 255.0f;
	}

	/**
	 *
	 *
	 * @param indices
	 * @param normalindices
	 */
	static void writeFaceWithNormals(final int[] indices, final int[] normalindices) {
		objWriter.print("f ");
		for (int i = 0; i < indices.length - 1; i++) {
			objWriter.print(indices[i] + "//" + normalindices[i] + " ");
		}
		objWriter.println(indices[indices.length - 1] + "//" + normalindices[indices.length - 1]);
	}

	/**
	 * Gets the curr normal offset.
	 *
	 * @return the curr normal offset
	 */
	static int getCurrNormalOffset() {
		return numNormalsWritten;
	}

	/**
	 * Gets the curr vertex offset.
	 *
	 * @return the curr vertex offset
	 */
	static int getCurrVertexOffset() {
		return numVerticesWritten;
	}

	/**
	 * Handle begin save.
	 */
	static void startWriting() {
		objWriter = new PrintWriter(objStream);
		objWriter.println("# generated by HET_OBJWriter");
		mtlWriter = new PrintWriter(mtlStream);
		mtlWriter.println("# generated by HET_OBJWriter");
		numVerticesWritten = 0;
		numNormalsWritten = 0;
	}

	/**
	 *
	 *
	 * @param name
	 */
	static void startNewObject(final String name) {
		objWriter.println("o " + name);
	}

	/**
	 * Normal.
	 *
	 * @param n
	 *            the n
	 */
	static void writeNormal(final WB_Coord n) {
		objWriter.println("vn " + n.xd() + " " + n.yd() + " " + n.zd());
		numNormalsWritten++;
	}

	/**
	 * Vertex.
	 *
	 * @param v
	 *            the v
	 */
	static void writeVertex(final WB_Coord v) {
		objWriter.println("v " + v.xd() + " " + v.yd() + " " + v.zd());
		numVerticesWritten++;
	}

	/**
	 *
	 *
	 * @param i
	 * @param c
	 */
	static void writeVertexColor(final int i, final int c) {
		mtlWriter.println("newmtl v" + i);
		mtlWriter.println("Kd " + red(c) + " " + green(c) + " " + blue(c));
		mtlWriter.println("illum 0");
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param path
	 * @param name
	 */
	public static void saveMesh(final HE_Mesh mesh, final String path, final String name) {
		beginSave(path, name);
		addMesh(mesh);
		endSave();
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param path
	 * @param name
	 */
	public static void saveMeshNN(final HE_Mesh mesh, final String path, final String name) {
		beginSave(path, name);
		addMeshNN(mesh);
		endSave();
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithFaceColor(final HE_Mesh mesh, final String path, final String name) {
		beginSave(path, name);
		addMeshWithFaceColor(mesh, name);
		endSave();
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithVertexColor(final HE_Mesh mesh, final String path, final String name) {
		beginSave(path, name);
		addMeshWithVertexColor(mesh, name);
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMesh(final Collection<? extends HE_Mesh> meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMesh(mesh);
		}
		endSave();
	}

	public static void saveMesh(final HE_MeshCollection meshes, final String path, final String name) {
		beginSave(path, name);
		for (int i = 0; i < meshes.getNumberOfMeshes(); i++) {
			addMesh(meshes.getMesh(i));
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshNN(final Collection<? extends HE_Mesh> meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshNN(mesh);
		}
		endSave();
	}

	public static void saveMeshNN(final HE_MeshCollection meshes, final String path, final String name) {
		beginSave(path, name);
		for (int i = 0; i < meshes.getNumberOfMeshes(); i++) {
			addMeshNN(meshes.getMesh(i));
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithFaceColor(final Collection<? extends HE_Mesh> meshes, final String path,
			final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshWithFaceColor(mesh, name);
		}
		endSave();
	}

	public static void saveMeshWithFaceColor(final HE_MeshCollection meshes, final String path, final String name) {
		beginSave(path, name);
		for (int i = 0; i < meshes.getNumberOfMeshes(); i++) {
			addMeshWithFaceColor(meshes.getMesh(i), name);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithVertexColor(final Collection<? extends HE_Mesh> meshes, final String path,
			final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshWithVertexColor(mesh, name);
		}
		endSave();
	}

	public static void saveMeshWithVertexColor(final HE_MeshCollection meshes, final String path, final String name) {
		beginSave(path, name);
		for (int i = 0; i < meshes.getNumberOfMeshes(); i++) {
			addMeshWithVertexColor(meshes.getMesh(i), name);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMesh(final HE_Mesh[] meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMesh(mesh);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshNN(final HE_Mesh[] meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshNN(mesh);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithFaceColor(final HE_Mesh[] meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshWithFaceColor(mesh, name);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param meshes
	 * @param path
	 * @param name
	 */
	public static void saveMeshWithVertexColor(final HE_Mesh[] meshes, final String path, final String name) {
		beginSave(path, name);
		for (final HE_Mesh mesh : meshes) {
			addMeshWithVertexColor(mesh, name);
		}
		endSave();
	}

	/**
	 *
	 *
	 * @param mesh
	 */
	private static void addMesh(final HE_Mesh mesh) {
		final int vOffset = getCurrVertexOffset() + 1;
		final int nOffset = getCurrNormalOffset() + 1;
		startNewObject(new Long(mesh.getKey()).toString());
		// vertices
		final TLongIntMap keyToIndex = new TLongIntHashMap(10, 0.5f, -1L, -1);
		Iterator<HE_Vertex> vItr = mesh.vItr();
		HE_Vertex v;
		int i = 0;
		while (vItr.hasNext()) {
			v = vItr.next();
			keyToIndex.put(v.key(), i);
			writeVertex(v);
			i++;
		}
		vItr = mesh.vItr();
		while (vItr.hasNext()) {
			writeNormal(vItr.next().getVertexNormal());
		}
		// faces
		final Iterator<HE_Face> fItr = mesh.fItr();
		HE_Face f;
		HE_Halfedge he;
		while (fItr.hasNext()) {
			f = fItr.next();
			he = f.getHalfedge();
			final int n = f.getFaceOrder();
			final int[] indices = new int[n];
			final int[] normalindices = new int[n];
			for (i = 0; i < n; i++) {
				indices[i] = keyToIndex.get(he.getVertex().key()) + vOffset;
				normalindices[i] = keyToIndex.get(he.getVertex().key()) + nOffset;
				he = he.getNextInFace();
			}
			writeFaceWithNormals(indices, normalindices);
		}
	}

	/**
	 *
	 *
	 * @param mesh
	 */
	private static void addMeshNN(final HE_Mesh mesh) {
		final int vOffset = getCurrVertexOffset() + 1;
		startNewObject(new Long(mesh.getKey()).toString());
		// vertices
		final TLongIntMap keyToIndex = new TLongIntHashMap(10, 0.5f, -1L, -1);
		final Iterator<HE_Vertex> vItr = mesh.vItr();
		HE_Vertex v;
		int i = 0;
		while (vItr.hasNext()) {
			v = vItr.next();
			keyToIndex.put(v.key(), i);
			writeVertex(v);
			i++;
		}
		// faces
		final Iterator<HE_Face> fItr = mesh.fItr();
		HE_Face f;
		HE_Halfedge he;
		while (fItr.hasNext()) {
			f = fItr.next();
			he = f.getHalfedge();
			final int n = f.getFaceOrder();
			final int[] indices = new int[n];
			for (i = 0; i < n; i++) {
				indices[i] = keyToIndex.get(he.getVertex().key()) + vOffset;
				he = he.getNextInFace();
			}
			writeFace(indices);
		}
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param name
	 */
	private static void addMeshWithFaceColor(final HE_Mesh mesh, final String name) {
		final int vOffset = getCurrVertexOffset() + 1;
		final int nOffset = getCurrNormalOffset() + 1;
		objWriter.println("mtllib " + name + ".mtl");
		startNewObject(new Long(mesh.getKey()).toString());
		// vertices
		final TLongIntMap keyToIndex = new TLongIntHashMap(10, 0.5f, -1L, -1);
		Iterator<HE_Vertex> vItr = mesh.vItr();
		HE_Vertex v;
		int i = 0;
		while (vItr.hasNext()) {
			v = vItr.next();
			keyToIndex.put(v.key(), i);
			writeVertex(v);
			i++;
		}
		vItr = mesh.vItr();
		while (vItr.hasNext()) {
			writeNormal(vItr.next().getVertexNormal());
		}
		// faces
		final Iterator<HE_Face> fItr = mesh.fItr();
		HE_Face f;
		HE_Halfedge he;
		int fi = 0;
		while (fItr.hasNext()) {
			f = fItr.next();
			he = f.getHalfedge();
			writeFaceColor(fi, f.getColor());
			objWriter.println("usemtl f" + fi++);
			final int n = f.getFaceOrder();
			final int[] indices = new int[n];
			final int[] normalindices = new int[n];
			for (i = 0; i < n; i++) {
				indices[i] = keyToIndex.get(he.getVertex().key()) + vOffset;
				normalindices[i] = keyToIndex.get(he.getVertex().key()) + nOffset;
				he = he.getNextInFace();
			}
			writeFaceWithNormals(indices, normalindices);
		}
	}

	/**
	 *
	 *
	 * @param mesh
	 * @param name
	 */
	private static void addMeshWithVertexColor(final HE_Mesh mesh, final String name) {
		final int vOffset = getCurrVertexOffset() + 1;
		final int nOffset = getCurrNormalOffset() + 1;
		objWriter.println("mtllib " + name + ".mtl");
		startNewObject(new Long(mesh.getKey()).toString());
		// vertices
		final TLongIntMap keyToIndex = new TLongIntHashMap(10, 0.5f, -1L, -1);
		Iterator<HE_Vertex> vItr = mesh.vItr();
		HE_Vertex v;
		int i = 0;
		while (vItr.hasNext()) {
			v = vItr.next();
			writeVertexColor(i, v.getColor());
			objWriter.println("usemtl v" + i);
			keyToIndex.put(v.key(), i);
			writeVertex(v);
			i++;
		}
		vItr = mesh.vItr();
		while (vItr.hasNext()) {
			writeNormal(vItr.next().getVertexNormal());
		}
		// faces
		final Iterator<HE_Face> fItr = mesh.fItr();
		HE_Face f;
		HE_Halfedge he;
		while (fItr.hasNext()) {
			f = fItr.next();
			he = f.getHalfedge();
			final int n = f.getFaceOrder();
			final int[] indices = new int[n];
			final int[] normalindices = new int[n];
			for (i = 0; i < n; i++) {
				indices[i] = keyToIndex.get(he.getVertex().key()) + vOffset;
				normalindices[i] = keyToIndex.get(he.getVertex().key()) + nOffset;
				he = he.getNextInFace();
			}
			writeFaceWithNormals(indices, normalindices);
		}
	}
}
